#include "jni_api.h"
#include <string.h>
#include <stdlib.h>
#include <typeinfo>
#include <cassert>

jobject JniHelper::c2j_float(JNIEnv* env, float value)
{
	jclass cls_obj = (env)->FindClass("java/lang/Float");
	jmethodID mid = (env)->GetMethodID(cls_obj, "<init>", "(F)V");
	return (env)->NewObject(cls_obj, mid, value);
}

jobject JniHelper::c2j_int(JNIEnv* env, int value)
{
	jclass cls_obj = (env)->FindClass("java/lang/Integer");
	jmethodID mid = (env)->GetMethodID(cls_obj, "<init>", "(I)V");
	return (env)->NewObject(cls_obj, mid, value);
}

jobject JniHelper::c2j_bool(JNIEnv* env, bool value)
{
	jclass cls_obj = (env)->FindClass("java/lang/Boolean");
	jmethodID mid = (env)->GetMethodID(cls_obj, "<init>", "(Z)V");
	return (env)->NewObject(cls_obj, mid, value);
}

jstring JniHelper::c2j_string(JNIEnv* env, const char* str) 
{
	jclass cls = (env)->FindClass("java/lang/String");
	jmethodID mid = (env)->GetMethodID(cls, "<init>", "([BLjava/lang/String;)V");
	jbyteArray bytes = (env)->NewByteArray(strlen(str));
	(env)->SetByteArrayRegion(bytes, 0, strlen(str), (jbyte*)str);
	jstring encoding = (env)->NewStringUTF("utf-8");
	return (jstring)(env)->NewObject(cls, mid, bytes, encoding);
}

jbyteArray JniHelper::c2j_bytes(JNIEnv* env, unsigned char* buf, int len)
{
	jbyteArray byteArray = (env)->NewByteArray(len);
	if(byteArray != 0 && buf)
	{
		(env)->SetByteArrayRegion(byteArray, 0, len, (jbyte*)buf);
	}

	return byteArray;
}

jshortArray JniHelper::c2j_shorts(JNIEnv* env, short* buf, int len)
{
	jshortArray shortArray = (env)->NewShortArray(len);
	if(shortArray != 0 && buf)
	{
		(env)->SetShortArrayRegion(shortArray, 0, len, (jshort*)buf);
	}
	return shortArray;
}

jintArray JniHelper::c2j_ints(JNIEnv* env, int* buf, int len)
{
	jintArray intArray = (env)->NewIntArray(len);
	if(intArray != 0)
	{
		(env)->SetIntArrayRegion(intArray, 0, len, (jint*)buf);
	}
	return intArray;
}

int JniHelper::j2c_int(JNIEnv* env, jobject obj)
{
	jclass cls_obj = (env)->FindClass("java/lang/Integer");
	jmethodID mid = (env)->GetMethodID(cls_obj, "intValue", "()I");
	return (int)(env)->CallIntMethod(obj, mid);
}

bool JniHelper::j2c_bool(JNIEnv* env, jobject obj)
{
	jclass cls_obj = (env)->FindClass("java/lang/Boolean");
	jmethodID mid = (env)->GetMethodID(cls_obj, "booleanValue", "()Z");
	return (env)->CallBooleanMethod(obj, mid) != 0;
}

float JniHelper::j2c_float(JNIEnv* env, jobject obj)
{
	jclass cls_obj = (env)->FindClass("java/lang/Float");
	jmethodID mid = (env)->GetMethodID(cls_obj, "floatValue", "()F");
	return (env)->CallFloatMethod(obj, mid) != 0;
}

std::string JniHelper::j2c_string(JNIEnv* env, jstring jstr) 
{
	std::string result;
	char* buffer = NULL; 
	jclass clsstring = env->FindClass("java/lang/String"); 
	jstring encode = (env)->NewStringUTF("utf-8");
	jmethodID mid = (env)->GetMethodID(clsstring, "getBytes", "(Ljava/lang/String;)[B");
	jbyteArray byteArray= (jbyteArray)(env)->CallObjectMethod(jstr, mid, encode);
	jsize len = (env)->GetArrayLength(byteArray);
	jbyte* bytes = (env)->GetByteArrayElements(byteArray, JNI_FALSE);
	if (len > 0) 
	{ 
		buffer = (char*)malloc(len + 1); 
		memcpy(buffer, bytes, len); 
		buffer[len] = 0;
		result = std::string(buffer);
	} 
	(env)->ReleaseByteArrayElements(byteArray, bytes, 0);

	if(buffer)
	{
		delete [] buffer;
		buffer = 0;
	}

	return result; 
}

int JniHelper::j2c_bytes(JNIEnv* env, jbyteArray array, unsigned char* buf, int len)
{
	size_t size = (env)->GetArrayLength(array);
	if(buf && len >= (int)size)
	{
		(env)->GetByteArrayRegion(array, 0, len, (jbyte*)buf);
	}
	return size;
}

int JniHelper::j2c_shorts(JNIEnv* env, jshortArray array, short* buf, int len)
{
	size_t size = (env)->GetArrayLength(array);
	if(buf && len > (int)size)
	{
		(env)->GetShortArrayRegion(array, 0, len, buf);
	}
	return size;
}

int JniHelper::j2c_ints(JNIEnv* env, jintArray array, int* buf, int len)
{
	size_t size = (env)->GetArrayLength(array);
	if(buf && len > (int)size)
	{
		(env)->GetIntArrayRegion(array, 0, len, (jint*)buf);
	}
	return size;
}

jclass JniHelper::getClassByName(JNIEnv* env, const char* name)
{
	return (env)->FindClass(name);
}

jclass JniHelper::getClassByObject(JNIEnv* env, jobject obj)
{
	return (env)->GetObjectClass(obj);
}

jfieldID JniHelper::getFieldID(JNIEnv*env, jclass cls, const char* name, const char* sig)
{
	jfieldID result = 0;
	do 
	{
		if(!cls || !name || !sig)
			break;

		result = (env)->GetFieldID(cls, name, sig);

	} while (false);
	return result;
}

jint JniHelper::getIntField(JNIEnv* env, jobject obj, jfieldID fid)
{
	assert(env && obj && fid);
	return (env)->GetIntField(obj, fid);
}

jobject JniHelper::getObjectField(JNIEnv* env, jobject obj, jfieldID fid)
{
	assert(env && obj && fid);
	return (env)->GetObjectField(obj, fid);
}

jchar JniHelper::getCharField(JNIEnv* env, jobject obj, jfieldID fid)
{
	assert(env && obj && fid);
	return (env)->GetCharField(obj, fid);
}

jshort JniHelper::getShortField(JNIEnv* env, jobject obj, jfieldID fid)
{
	assert(env && obj && fid);
	return (env)->GetShortField(obj, fid);
}

jboolean JniHelper::getBooleanField(JNIEnv* env, jobject obj, jfieldID fid)
{
	assert(env && obj && fid);
	return (env)->GetBooleanField(obj, fid);
}

jlong JniHelper::getLongField(JNIEnv* env, jobject obj, jfieldID fid)
{
	assert(env && obj && fid);
	return (env)->GetLongField(obj, fid);
}

void JniHelper::setIntField(JNIEnv* env, jobject obj, jfieldID fid, jint value)
{
	assert(env && obj && fid);
	return (env)->SetIntField(obj, fid, value);
}

void JniHelper::setObjectField(JNIEnv* env, jobject obj, jfieldID fid, jobject value)
{
	assert(env && obj && fid);
	return (env)->SetObjectField(obj, fid, value);
}

void JniHelper::setCharField(JNIEnv* env, jobject obj, jfieldID fid, jchar value)
{
	assert(env && obj && fid);
	return (env)->SetCharField(obj, fid, value);
}

void JniHelper::setShortField(JNIEnv* env, jobject obj, jfieldID fid, jshort value)
{
	assert(env && obj && fid);
	return (env)->SetShortField(obj, fid, value);
}

void JniHelper::setBooleanField(JNIEnv* env, jobject obj, jfieldID fid, jboolean value)
{
	assert(env && obj && fid);
	return (env)->SetBooleanField(obj, fid, value);
}

void JniHelper::setLongField(JNIEnv* env, jobject obj, jfieldID fid, jlong value)
{
	assert(env && obj && fid);
	return (env)->SetLongField(obj, fid, value);
}

JniOutputBuilder::JniOutputBuilder(JNIEnv* env)
{
	m_env = env;
}

jobjectArray JniOutputBuilder::getOutput()
{
	jobjectArray result = 0;

	if(_objs.size())
	{
		jclass cls_obj = (m_env)->FindClass("java/lang/Object");
		result = (m_env)->NewObjectArray(_objs.size(), cls_obj, 0);
		for (int i=0;i<(int)_objs.size();i++)
		{
			(m_env)->SetObjectArrayElement(result, i, _objs.at(i));
		}
	}
	
	return result;
}

JniOutputBuilder& JniOutputBuilder::add(jobject obj)
{
	_objs.push_back(obj);
	return *this;
}

JniOutputBuilder& JniOutputBuilder::add(int value)
{
	jobject obj = JniHelper::c2j_int(m_env, value);
	_objs.push_back(obj);
	return *this;
}

JniOutputBuilder& JniOutputBuilder::add(float value)
{
	jobject obj = JniHelper::c2j_float(m_env, value);
	_objs.push_back(obj);
	return *this;
}

JniOutputBuilder& JniOutputBuilder::add(bool value)
{
	jobject obj = JniHelper::c2j_bool(m_env, value);
	_objs.push_back(obj);
	return *this;
}

JniOutputBuilder& JniOutputBuilder::add(std::string value)
{
	jobject obj = JniHelper::c2j_string(m_env, value.c_str());
	_objs.push_back(obj);
	return *this;
}

JniOutputBuilder& JniOutputBuilder::add(unsigned char* byteArray, int len)
{
	jobject obj = JniHelper::c2j_bytes(m_env, byteArray, len);
	_objs.push_back(obj);
	return *this;
}

JniOutputBuilder& JniOutputBuilder::add(short* shortArray, int len)
{
	jobject obj = JniHelper::c2j_shorts(m_env, shortArray, len);
	_objs.push_back(obj);
	return *this;
}

JniOutputBuilder& JniOutputBuilder::add(int* intArray, int len)
{
	jobject obj = JniHelper::c2j_ints(m_env, intArray, len);
	_objs.push_back(obj);
	return *this;
}